/*********************************************************************************
 *      Copyright:  (C) 2025 LingYun<iot25@lingyun>
 *                  All rights reserved.
 *
 *       Filename:  poll.c
 *    Description:  This file 
 *                 
 *        Version:  1.0.0(24/03/25)
 *         Author:  LingYun <iot25@lingyun>
 *      ChangeLog:  1, Release initial version on "24/03/25 21:04:12"
 *                 
 ********************************************************************************/
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<unistd.h>
#include<ctype.h>
#include<stdlib.h>
#include<pthread.h>
#include<time.h>
#include<getopt.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<stdlib.h>
#include<libgen.h>
#include<poll.h>

#define ARRAY_SIZE(x)      (sizeof(x)/sizeof(x[0]))
 static inline void print_usage( char *progname);
int socket_server_init(char *listen_ip,int listen_port);
static inline void msleep( unsigned long ms);
int main( int argc, char  *argv[])
{
	int               	listenfd ,connfd;
	int 			    serv_port = 0;
	int 			  	daemon_run = 0;
	char				*progname = NULL;
	int					opt;// 存储当前解析到的选项字符（如 'b'、'p'、'h'）
	int					rv;
	int					i, j;
	int					found;
	int					max;
	char				buf[1024];
	struct pollfd		fds_array[1024];

	struct option		long_options[]=
	{
		{ "daemon",  no_argument, NULL , 'b'},
		{ "port", required_argument, NULL,'p'},
		{ "help", no_argument ,NULL ,'h'},
		{NULL, 0 ,NULL,0}
	};


	progname=basename(argv[0]);

	while(( opt = getopt_long(argc, argv,"bp:h", long_options, NULL)) != -1)//每次调用 getopt_long 解析一个选项，返回选项字符（如 'b'）或 -1（结束）。b：无参选项（如 -b）。 p:：带参选项（冒号表示 -p 后必须跟参数，如 -p 8080）。 h：无参选项（如 -h）long_options：长选项映射表，将 --daemon 映射到 'b'，--port 映射到 'p'。
//	longindex：通常设为 NULL（不返回长选项的索引）。
	{
		switch (opt)
		{
			case 'b':
				daemon_run=-1;
				break;

			case 'p':
				serv_port = atoi(optarg);// 将参数转换为整数（如 "8080" → 8080）
				break;

			case 'h':
				print_usage(progname);
				return EXIT_SUCCESS; // 直接退出程序

		    default:
				break;
		}
	}

	if( !serv_port )//检查port是否合法
	{
		print_usage(progname);
		return -1;
	}

	if( (listenfd=socket_server_init(NULL, serv_port)) <0 )//对socket服务器进行初始化
	{
		printf(" ERROR : %s server listen on port %d failure\n", argv[0], serv_port);
		return -2;
	}
	printf("%s server start to listen on port %d\n",argv[0], serv_port);


	if( daemon_run )//守护进程后台运行
	{
		daemon( 0 , 0 );//参数0，不改变工作目录， 0：不重定向IO

	}

	for( i=0 ; i<ARRAY_SIZE(fds_array) ; i++)//初始化pollfd数组
	{
		fds_array[i].fd= -1;//初始化为无效fd
	}
	fds_array[0].fd = listenfd;//将监听socket放在数组的首位
	fds_array[0].events = POLLIN;//监听可读事件

	max = 0;//当前最大有效索引为0

	for( ; ; )
	{
		rv= poll(fds_array, max+1, -1);//调用poll（）监听所有socket，无限等待-1
		if( rv<0 )//错误处理
		{
			printf("select failure: %s\n", strerror(errno));
			break;
		}
		else if( rv ==0 )//超时（但这里设置了无限等待，不会触发。
		{
			printf("select get timeout\n");
			continue;
		}

		if( fds_array[0].revents & POLLIN)//检查监听套接字是否有新的连接。
		{
			if( (connfd=accept(listenfd, (struct sockaddr *)NULL, NULL)) <0 )//接受新连接
			{
				printf("accept new client failure: %s\n", strerror(errno));
				continue;
			}

			found = 0;//寻找空闲位置储存新连接
			for(i=1; i<ARRAY_SIZE(fds_array) ; i++)// 遍历pollfd数组（跳过索引0，因为0是监听套接字）
			{
				if( fds_array[i].fd < 0 ) // 找到一个未使用的fd（-1表示空闲）
				{
					printf("accept new client[%d] and addr it into array\n",connfd );
					fds_array[i].fd = connfd; // 存储新客户端连接的文件描述符
					fds_array[i].events = POLLIN; // 监听该fd的可读事件（数据到达）
					found = 1;                             // 标记已找到空闲位置

					break;
				}
			}

			if( !found )// 连接数已达上限
			{
				printf( "accept new client[%d] but full , so rerfuse it\n", connfd);
				close(connfd);
				continue;
			}

			max = i>max ? i :max;//在成功接受连接后，通过 max = i > max ? i : max 确保 poll() 能监听到所有有效fd。// 更新最大索引
			if( --rv <= 0)
/*代码	作用
--rv	扣除已处理的新连接事件，更新剩余待处理事件数。
<= 0	如果无其他事件，直接跳过后续处理（优化性能）。
设计目的	避免无意义的遍历，优先保证新连接的响应速度。*/
				continue;
		}
		else
		{
			for(i=1; i<ARRAY_SIZE(fds_array); i++)//i = 1：跳过索引 0（监听套接字 listenfd）。ARRAY_SIZE(fds_array)：遍历整个数组（通常大小为1024）。
			{
				if(fds_array[i].fd < 0 )//表示该位置未存储有效的客户端套接字（可能是之前关闭的连接）。
					continue;
				
				//读取客户端数据
				if( (rv=read(fds_array[i].fd, buf, sizeof(buf))) <= 0)
				{
					printf("socket[%d] read failure or get disconnect.\n", fds_array[i].fd);
					close(fds_array[i].fd);//关闭数组
					fds_array[i].fd = -1;//标记为无效
				}
				else
				{
					printf("socket[%d] read get %d bytes data\n",fds_array[i].fd , rv);
					
					for(j=0; j<rv; j++)//
						buf[j]=toupper(buf[j]);

					if( write(fds_array[i].fd, buf , rv)<0 )//尝试将缓冲区 buf 中的 rv 字节数据写入客户端套接字。
					{
						printf("socket[%d] writer failure: %s\n", fds_array[i].fd, strerror(errno));//输出套接字描述符和具体错误原因（通过 strerror(errno)）。
						close(fds_array[i].fd);//关闭数组
						fds_array[i].fd = -1;//标记为无效
					}
				}
			}
		}

	}
CleanUp:
		close(listenfd);
		return 0;
}
static inline void msleep( unsigned long ms)
 {
     struct timeval      tv;
 
     tv.tv_sec = ms/1000;//设置秒数
     tv.tv_usec = (ms%1000)*1000;//设置微秒数
     select( 0 , NULL , NULL , NULL , &tv); //利用select实现精准休眠
  }
 
 //打印帮助信息
static inline void print_usage( char *progname)
 {
     printf("Usage: %s [OPTION]...\n", progname);
 
     printf(" %s is a socket server program , which used to verify client and echo back string         from it\n", progname);
     printf(" \n Mandatory arguments to long options are mandatory for short options too :\n");
 
     printf(" -b[daemon ] set program running on background\n");
     printf(" -p[port ] Socket server port address\n");
     printf(" -h[help ] Display this help information\n");
 
     printf(" \nExample: %s -b -p 8900\n", progname);
     return;
 }
//初始化tcp服务器套接字
int socket_server_init(char *listen_ip,int listen_port)
 {
     struct sockaddr_in          servaddr; //IPV4结构
     int                         rv = 0;
     int                         on = 1;//套接字选项开关
     int                         listenfd;//监听套接字描述符
 
     if( (listenfd = socket(AF_INET, SOCK_STREAM , 0 )) < 0 )//创建套接字
     {         printf(" use socket() to create a TCP socket failure: %s\n", strerror(errno));
         return -1;
     }
 
     setsockopt( listenfd, SOL_SOCKET, SO_REUSEADDR, &on , sizeof(on));//                              设置端口复用（防止TIME_WAIT状态导致连接失败）
 
     memset(&servaddr, 0 , sizeof(servaddr) );
     servaddr.sin_family = AF_INET;// IP v4协议
     servaddr.sin_port = htons(listen_port);//设置端口（主机字节序转成网络字节序）
 
     if( !listen_ip )//监听所有IP或指定IP
     {
         servaddr.sin_addr.s_addr = htonl(INADDR_ANY );
     }
     else//将字符串转换为二进制格式
     {
         if(inet_pton(AF_INET,listen_ip,&servaddr.sin_addr) <= 0)
         {
             printf("inet_pton() set listen IP address failure\n");
             rv = -2;
             goto Cleanup;
         }
     }
 //绑定套接字到IP端口
     if( bind( listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr) )<0 )
     {
         printf(" use bind() to bind the TCP socket failure: %s\n",strerror(errno) );
         rv = -3;
         goto Cleanup;
     }
 
     //开始监听，最大排队数位13
     if( listen(listenfd, 13)<0 )
     {
         printf(" use bind() to bind the TCP socket failure: %s\n",strerror(errno) );

         rv = -4;
         goto Cleanup;
     }
 Cleanup:
     if( rv<0 )
         close(listenfd);//失败时关闭套接字
     else
         rv = listenfd;//成功返回监听套接字
 
     return rv;
 
 }

